#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/signalfd.h>
#include <signal.h>

#include "socket.h"
#include "func_config.h"
#include "func_strings.h"
#include "log.h"
#include "func_socket.h"
#include "TratamientoPlanif.h"
#include "constantes.h"
#include "estructuras.h"
#include "structs_commons.h"
#include "colas.h"
#include "func_ipc.h"
#include "ComPerifericos.h"
#include "constants_commons.h"


#define HANDSHAKE 0
#define LECTURA 1
#define ESCRITURA 2

#define SincRead  1
#define SincWrite 2
#define ComRead   3
#define ComWrite  4



void ErrorRecv(int bytesRecibidos, int socket, t_log *log){
	if(bytesRecibidos == 0){
	//conexion cerrada
		perror("recv");
		log_error(log, SNOMBREPROCESO, "Message error: %s", "Conexion cerrada por el cliente");
	}
	else{
		perror("recv");
		log_error(log, SNOMBREPROCESO, "Message error: %s", "Error al recibir los datos");
	}
	close(socket);
}

int SincronizarDisco(int sectores, int socket){
	//pido a los discos los sectores segun algortimo planif
	nodoLista *aux, *elegido;
	stPEscrituraSector *sector;
	int min = 1000;
	int i;

	for(i = 0; i < sectores; i++){
		aux = glob_listaDiscos->sgte;//le asigno el 2do, xq en el 1ro esta el disco que quiero sincronizar
/*		while(aux->sgte != NULL){
			if(aux->data.cantPed < min && aux->data.sincronizado == 1){
				elegido = aux;
				min = aux->data.cantPed;
			}
			aux = aux->sgte;
		}*/
		while(aux != NULL){
			if(aux->data.cantPed < min && aux->data.sincronizado == 1){
				elegido = aux;
				//min = aux->data.cantPed;
				min = aux->data.cantPed + 1;
			}
			aux = aux->sgte;
		}



		sector->iSector = i;
		memset(sector->info, 0, 512);
		InsertarPedido(&elegido->data.finCola, sector, SincRead, socket);//Inserto el pedido en la cola;i es el sector a buscar
		//envio una señal al hilo del ppd diciendole que tiene pedidos en la cola
		pthread_kill(aux->data.idThread, SIGUSR2);
	}

	return 0;//fijarse bien (0 si dio bien, negativo mal)
}

static pthread_mutex_t glob_MutexPlanif = PTHREAD_MUTEX_INITIALIZER;
int planifConectados = 0;

void SumarizarPlanificador(void){
	pthread_mutex_lock(&glob_MutexPlanif);

	planifConectados++;

	pthread_mutex_unlock(&glob_MutexPlanif);
}

void RestarPlanificador(void){
	pthread_mutex_lock(&glob_MutexPlanif);

	planifConectados--;

	pthread_mutex_unlock(&glob_MutexPlanif);
}

void ComPerifericos(void *log){

	//int planifConectados = 0;
	int serverSock;
	char puertoLocal[6] = "\0";
	int fdmax;
	int newSock;
	int i;
	int cliente;
	int escuchando;//se utiliza para saber si el socket servidor esta escuchando correctamente
	int nbytes;//cantidad de bytes que recibo en el recv
	stNIPC *headerNIPC;
	datosConfig stInfoConfig;
	nodoLista *lista, *aux, *nuevoDisco;
	lista = NULL;//inicializamos la lista en null
	int tipoPedido;
	int sincOK;



	glob_listaDiscos = NULL;


	stInfoPpd *infoPpd;

	t_stream *stream = malloc(sizeof(t_stream));
	char* cadHeader;
	int iMaxBuffer;

	stPEscrituraSector *pedidoEscritura;
	int pedLectFs;


	fd_set descLectura, master;
	pthread_t hiloPlanif;

	argThread *argHilo;
	//argHilo = (argThread)malloc(sizeof(argThread));

	//DECLARO SIGNALFD(sirve para que el hilo ppd sepa cuando tiene algo en la cola)
	sigset_t mask;
	int sfd;
	sigemptyset(&mask);  //Limpio el sigset
	sigaddset(&mask, SIGUSR2);  //Agrego la señal que quiero escuchar
	if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
		log_error(log, SNOMBREPROCESO, "Message error: %s", "");
	sfd = signalfd(-1, &mask, 0); //Creo el signalfd
	if (sfd == -1)
		log_error(log, SNOMBREPROCESO, "Message error: %s", "Error al crear signalfd");


	//Cargamos archivo de configuracion
	if(fLoadConfig(&stInfoConfig, log)){
		printf("No se pudo procesar el archivo de configuracion");
		log_error(log, SNOMBREPROCESO, "Message error: %s", "No se pudo procesar el archivo de configuracion");
		log_destroy(log);
		return;
	}

	pIntacad(stInfoConfig.iPuerto, puertoLocal);

	serverSock = fPrepararSocket(puertoLocal, stInfoConfig.sIp, "Socket para atender conexiones entrantes", 0, LOG, SNOMBREPROCESO, log);
	if(serverSock == -1){
		log_error(log, SNOMBREPROCESO, "Message error: %s", "Error al crear el socket servidor");
		//perror("Error al crear el socket servidor");
		log_destroy(log);
		return;
	}
	escuchando = fEscuchar(serverSock, 100, LOG, SNOMBREPROCESO, log);
	if(escuchando == 0){
		log_error(log, SNOMBREPROCESO, "Message error: %s", "Error al escuchar en el socket servidor");
		log_destroy(log);
		return;
	}

	FD_ZERO(&descLectura);
	FD_ZERO(&master);
	FD_SET(serverSock, &master);

	fdmax = serverSock;

	//despues borrar, es solo para que me muestre si asigno bien ip y puerto
	puts(stInfoConfig.sIp);
	puts(puertoLocal);

	//bucle principal
	while(1){
		descLectura = master;
		if(select(fdmax + 1, &descLectura, NULL, NULL, NULL) == -1){
			perror("select");
			log_error(log, SNOMBREPROCESO, "Message error: %s", "Error en el SELECT PPAL");
			return;
		}


		for(i = 0; i <= fdmax; i++){

			if(FD_ISSET(i, &descLectura)){//hay datos

				if(i == serverSock){
					newSock = AceptarConexion(serverSock);
					if(newSock == -1){
						log_error(log, SNOMBREPROCESO, "Message error: %s", "Error al aceptar socket cliente");
					}

					//cadHeader = malloc(sizeof(char)*16+sizeof(int)+sizeof(short int)+sizeof(int));
					//iMaxBuffer= (sizeof(char)*16)+sizeof(int)+sizeof(short int)+sizeof(int);
					iMaxBuffer= sizeof(stNIPC);
					cadHeader = (char*)malloc(iMaxBuffer);

					//recibimos la cabecera
					if((nbytes = recv(newSock, cadHeader, iMaxBuffer, MSG_WAITALL)) <= 0){
						ErrorRecv(nbytes, serverSock, log);
						FD_CLR(serverSock, &master);
					}
					else{//cabecera correcta, tratamos el pedido
						stream->data = cadHeader;
						stream->length = iMaxBuffer;

						headerNIPC = malloc(sizeof(stNIPC));

						stNIPC_deserializer(stream, headerNIPC);
						//analizo la cabecera para ver quien es el que me hablo(FS o PPD)

						cliente = headerNIPC->iPayloadDescriptor;

						switch(cliente){
						case CONNECT_PFS:
							if(headerNIPC->iType == HANDSHAKE){
								if(planifConectados > 0){
									FD_SET(newSock, &master);
									if(newSock > fdmax)
										fdmax = newSock;

									//cargo estructura
									headerNIPC->iType = 0;
									headerNIPC->sPayload_lenth = 0;
									headerNIPC->iPayloadDescriptor = CONNECT_OK;
									memset(headerNIPC->sDescriptorOriginal, 0, 16);
									//envio la respuesta de conectado ok
									send(newSock, (void*)headerNIPC, sizeof(stNIPC), 0);
								}
								else{
									//rechazo la conexion y cierro el socket cliente
									log_error(log, SNOMBREPROCESO, "Message error %s", "El Raid aun no esta activo");

									headerNIPC->iType = 0;
									memset(headerNIPC->sDescriptorOriginal, 0, 16);
									headerNIPC->iPayloadDescriptor = CONNECT_FAIL;
									headerNIPC->sPayload_lenth = 0;

									//envio respuesta de rechazo
									send(newSock, (void*)headerNIPC, sizeof(stNIPC), 0);
									close(newSock);
								}
							}
							else{
								//cierro la conexion porque me envio algo distinto a HANDSHAKE
								log_error(log, SNOMBREPROCESO, "Message error: %s", "Solicitud incorrecta");
								headerNIPC->iType = 0;
								memset(headerNIPC->sDescriptorOriginal, 0, 16);
								headerNIPC->iPayloadDescriptor = CONNECT_FAIL;
								headerNIPC->sPayload_lenth = 0;

								//envio respuesta de rechazo
								send(newSock, (void*)&headerNIPC, sizeof(stNIPC), 0);
								close(newSock);
							}
							break;

						case CONNECT_PPD:
							if(headerNIPC->iType == HANDSHAKE){
								infoPpd = malloc(sizeof(stInfoPpd));

								if((nbytes = recv(newSock, (char*)infoPpd, headerNIPC->sPayload_lenth, 0)) <= 0)
									ErrorRecv(nbytes, serverSock, log);
								else{
									puts(infoPpd->idPPD);
									printf("cantsect %d", infoPpd->cantSect);

									if(Pertenece(infoPpd->idPPD) == 0){
										//no se encuentra en la lista, entonces es valido

										//planifConectados = planifConectados + 1;
										SumarizarPlanificador();

										//AGREGO EL DISCO A LA LISTA


										nuevoDisco = (nodoLista*)malloc(sizeof(nodoLista));

										nuevoDisco->data.cantPed = 0;
										strcpy(nuevoDisco->data.idPPD, infoPpd->idPPD);
										nuevoDisco->data.sockPPD = serverSock;
										if(planifConectados == 1)//si es el primero ya esta sincronizado
											nuevoDisco->data.sincronizado = 1;
										else
											nuevoDisco->data.sincronizado = 0;
										nuevoDisco->data.iniCola = NULL;
										nuevoDisco->data.finCola = NULL;
										nuevoDisco->sgte = NULL;
										AgregarDisco(nuevoDisco);//ver donde se hace el free


										argHilo->log = log;
										argHilo->signalFd = sfd;

										//CREO EL HILO DEL DISCO Y LE PASO EL SIGNALFD PARA SABER SI HAY ACTIVIDAD EN LA COLA
										//if(pthread_create(&hiloPlanif, NULL, (void*)&TratamientoPlanif, (void*)&sfd) != 0){
										if(pthread_create(&hiloPlanif, NULL, (void*)&TratamientoPlanif, (void*)&argHilo) != 0){
											log_error(log, SNOMBREPROCESO, "Message info: %s", "Error al crear hilo PPD");
											close(newSock);
										}

										glob_listaDiscos->data.idThread = hiloPlanif;

										sectoresDisco = infoPpd->cantSect;

										if(planifConectados > 1){//si es el primero que se me conecta no lo sincronizo
											if((sincOK = SincronizarDisco(sectoresDisco, nuevoDisco->data.sockPPD)) <= 0){
												//quitarlo de la lista
												log_error(log, SNOMBREPROCESO, "Message info: %s", "Error al sincronizar el disco");
											}
										}
										log_info(log, SNOMBREPROCESO, "Message info: Nuevo PPD Conectado: %s", infoPpd->idPPD);

										headerNIPC->iPayloadDescriptor = CONNECT_OK;
										headerNIPC->iType = HANDSHAKE;
										memset(headerNIPC->sDescriptorOriginal, 0, 16);
										headerNIPC->sPayload_lenth = 0;
										send(newSock, (void*)&headerNIPC, sizeof(stNIPC), 0);
									}
									else{//el ppd ya se encuentra en la lista
										//rechazo la conexion y cierro el socket cliente
										log_error(log, SNOMBREPROCESO, "Message Error: %s", "El disco ya se encuentra conectado");

										headerNIPC->iType = 0;
										memset(headerNIPC->sDescriptorOriginal, 0, 16);
										headerNIPC->iPayloadDescriptor = CONNECT_FAIL;
										headerNIPC->sPayload_lenth = 0;

										send(newSock, (void*)&headerNIPC, sizeof(stNIPC), 0);
										close(newSock);
									}
								}
							}
							else
								close(newSock);

							break;

						}
						free(headerNIPC);
					}
				}
				//////////////////////////////////////////
				////////LECTURA ESCRITURA DEL FS//////////
				//////////////////////////////////////////

				if(i == newSock){
					//cadHeader = malloc(sizeof(char)*16+sizeof(int)+sizeof(short int));
					headerNIPC = malloc(sizeof(char)*16+sizeof(int)+sizeof(short int));
					iMaxBuffer= (sizeof(char)*16)+sizeof(int)+sizeof(short int);
					//recibimos la cabecera
					//if((nbytes = recv(newSock, cadHeader, iMaxBuffer, 0)) <= 0){
					if((nbytes = recv(newSock, headerNIPC, iMaxBuffer, 0)) <= 0){
						//error : conexion cerrada por el cliente
						ErrorRecv(nbytes, newSock, log);
						FD_CLR(serverSock, &master);
					}
					else{//cabecera correcta, tratamos el pedido
						//stream->data = cadHeader;
						//stream->length = iMaxBuffer;
						//stNIPC_deserializer(stream, headerNIPC);

						//PEDIDOS DE LECTURA/ESCRITURA DEL FS
						switch(headerNIPC->iType){
						case LECTURA:
							pedLectFs = (int)malloc(sizeof(int));
							if((nbytes = recv(newSock, (void*)&pedLectFs, headerNIPC->sPayload_lenth, 0)) <= 0)
								ErrorRecv(nbytes, newSock, log);
							else{
								tipoPedido = R;
								EncolarPedido(lista, pedLectFs, tipoPedido, newSock);
							}
							break;
						case ESCRITURA:
							pedidoEscritura = (stPEscrituraSector*)malloc(sizeof(stPEscrituraSector));
							if((nbytes = recv(newSock, (stPEscrituraSector*)pedidoEscritura, headerNIPC->sPayload_lenth, 0)) <=0)
								ErrorRecv(nbytes, newSock, log);
							else{
								aux= lista;
								while(aux != NULL){
									tipoPedido = W;
									InsertarPedido(&aux->data.finCola, pedidoEscritura, tipoPedido, newSock);//INSERTO EL PEDIDO EN LA COLA DE CADA NODO DE LA LISTA
									aux = aux->sgte;
								}
							}
							break;
						}//fin switch
					}//fin else pedidos lectura/escritura FS
				}//fin select i=newsock
			}
		}
		//return;
	}
}



















